/**
 * 
 */
package com.redhat.auditor.webdiff;

import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import org.xml.sax.SAXException;

/**
 * {@link com.redhat.auditor.webdiff.WebDiff}
 * <p>
 * This class is the main entry point to WebDiff functionality. It is an
 * abstract class that provides a number of global methods to give controlled
 * access to the underlying WebDiff implementation.
 * <p>
 * The purpose of having an abstract class (or in the ideal circumstance a pure
 * interface) as the entry point for a component such as WebDiff is to allow its
 * implementation detail to be decoupled from its interface. This is designed as
 * such in the hope that any system consuming WebDiff's functionality will be
 * allowed to retain the same view of the WebDiff interface even as the WebDiff
 * implementation changes over time, meaning that WebDiff clients will require
 * little or no maintenance. This follows the ideal of a loosely-coupled, highly
 * configurable and robust component design.
 * 
 * @version 0.1
 * @author Trungtin Ton
 * 
 */
public abstract class WebDiff {

	/**
	 * TODO write and publish the WebDiff public API
	 * 
	 * @note (needs to include specification for the usage of all WebDiff
	 *       operations)
	 */

	/**
	 * Helper constant representing the string literal used to specify a compare
	 * operation.
	 */
	public static final String COMPARE_OPERATION = "compare";
	/**
	 * The single instance of the underlying WebDiff instance used globally by
	 * this class throughout runtime.
	 */
	private static final WebDiff instance = new WebDiffImpl();

	/**
	 * Main WebDiff entry point. As a minimum, it should provide the
	 * functionality for executing a WebDiff CompareServlet operation. These
	 * WebDiff operations can be configured and invoked by passing a payload of
	 * arguments to this function's parameters.
	 * <p>
	 * Essentially the use of a flexible, dynamically loaded var-args method
	 * like this one is to provide a very loosely-coupled alternative to the
	 * more commonly used overloaded methods.
	 * <p>
	 * The implication is that because the request message (or payload) is
	 * parsed and determined dynamically at runtime, and the interface knows
	 * very little about what implementations may be provided underneath and
	 * how, there could be a higher risk of minor programmatic errors preventing
	 * the requested operation from executing successfully, in comparison to a
	 * strictly declared and strongly typed method signature where errors can be
	 * detected at compile time. However, provided that instructions for the use
	 * of the various operations are well documented in a public API, the former
	 * is desirable for its loosely-coupled nature and maintainability
	 * (particularly for clients, as they do not need to alter their source code
	 * every time updates are made to the API, making this design pattern
	 * particularly useful for service-orientation where clients cannot depend
	 * on old versions).
	 * <p>
	 * The specification for different WebDiff operations and their usage,
	 * including schemas for specifying payloads, should be exposed via the
	 * WebDiff public API.
	 * 
	 * @param operation
	 *            the name of one of the available WebDiff operations specified
	 *            in the WebDiff public API to be executed.
	 * @param writer
	 *            the {@link Writer} to write the result with.
	 * @param args
	 *            An array of optional arguments containing data passed in by
	 *            the client. These arguments may specify the requested
	 *            operation, inputs, configuration options and processing
	 *            instructions for use during execution. They will be checked by
	 *            the underlying WebDiff instance for the necessary arguments to
	 *            successfully execute the requested operation.
	 * 
	 */
	public static void execute(final String operation, final Writer writer,
			final Object... args) {
		/**
		 * DEV NOTES:
		 * 
		 * @note this method <i>should not</i> be made to execute any operations
		 *       itself; i.e. the body of this method it should not contain any
		 *       operational code. Its purpose is to receive general requests,
		 *       interpret (parse) those requests and invoke the corresponding
		 *       operation (or operations) which should be executed in a
		 *       separate method, class or component (possibly even another
		 *       Enterprise Java Bean, or a Web Service).
		 *       <p>
		 *       This method is designed with a purely coordinative role in
		 *       mind: a controller (or in service-orientation terms, a
		 *       process-centric function) providing a single, universal entry
		 *       point to all of the available operations.
		 *       <p>
		 *       The concept behind the use of this method is based on payload
		 *       semantics and has similarities to dynamically typed languages
		 *       such as JavaScript, or to command line execution where any text
		 *       command can be entered at runtime and the system will try to
		 *       interpret the command as best as it can.
		 *       <p>
		 *       Using these design patterns, the system can maintain a robust
		 *       and uniform facade to the applications it interfaces with,
		 *       whilst having a highly flexible, extensible and configurable
		 *       internal implementation.
		 *       <p>
		 *       This means that whenever a feature is to be added or changed,
		 *       the entire codebase does not need to be overhauled, nor does
		 *       the application interface - instead, an external library can be
		 *       attached, and then a simple invocation added to this
		 *       controller.
		 */
		try {
			System.out.println("WebDiff: begin.");

			// Map for holding additional data and arguments:
			final Map<String, Object> options = new HashMap<String, Object>();

			// OPERATIONS:

			/* @operation WEBDIFF COMPARE */
			if (COMPARE_OPERATION.equals(operation)) {
				/*
				 * String[] arg; String key, value; // Check the method's
				 * optional arguments for any useful data for (int i = 2; i <
				 * args.length; i++) { arg = args[i].split("="); key = arg[0];
				 * value = arg[1]; if (key.equalsIgnoreCase("--css")) { // array
				 * of CSS links options.put("css", value.split(";")); } }
				 */
				instance.doCompare((InputStream) (args[0]),
						(InputStream) (args[1]), writer, options);
			} /* @end WEBDIFF COMPARE */

			// END OPERATIONS
			else {
				throw new UnsupportedOperationException(
						operation
								+ " is not an operation currently supported by WebDiff. For the list and more information on supported operations, see the WebDiff public API.");
			}

		} catch (final Throwable e) {
			// XXX Should errors be caught and handled or should they be raised?
			System.out.println("WebDiff: error.");
			handle(e);

		} finally {
			System.out.println("WebDiff: complete.");
		}
	}

	/**
	 * The low-level, direct access method for the WebDiff compare operation.
	 * <p>
	 * This method is provided as a strict interface semantic alternative to the
	 * more flexible {@link #execute} function. It may be used by clients that
	 * wish to build their own abstracted interfaces over the top of this lower
	 * level operational interface. Alternatively it can be used if strictness
	 * in method parameters is required; however programmers using this method
	 * should be aware that updates to this method are unlikely to occur and the
	 * use of this method may place restrictions on future extensibility.
	 * 
	 * @param srcDocument
	 *            the reference location, for example a URI or file path, of the
	 *            source (old) document
	 * @param destDocument
	 *            the reference location, for example a URI or file path, of the
	 *            destination (new) document
	 * @param writer
	 *            the writer with which to write the result to the client
	 * @param options
	 *            a mapping of optional arguments to supply to the operation
	 */
	public static void compare(final InputStream srcDocument,
			final InputStream destDocument, final Writer writer,
			final Map<String, Object> options) {
		try {
			System.out.println("WebDiff: begin.");
			instance.doCompare(srcDocument, destDocument, writer, options);

		} catch (final Throwable e) {
			System.out.println("WebDiff: error.");
			handle(e);
		} finally {
			System.out.println("WebDiff: complete.");
		}
	}

	/**
	 * Executes the default handling process for all throwables raised to the
	 * current level of the call stack, thus preventing it from being further
	 * elevated.
	 * <p>
	 * This method should generally be called when the exception or error
	 * reaches a critical point without having yet been caught or handled and
	 * should not be allowed to travel further up the heirarchy, for example due
	 * to security or the risk of crashing the system. Any such errors passed
	 * into this method are logged to the default console or log file, and then
	 * die quietly.
	 * 
	 * @param e
	 *            the throwable that has been thrown and must be handled.
	 * @return the given throwable.
	 */
	private static Throwable handle(final Throwable e) {
		e.printStackTrace();

		if (e.getCause() != null) {
			e.getCause().printStackTrace();
		}
		if (e instanceof SAXException) {
			((SAXException) e).getException().printStackTrace();
		}
		return e;
	}

	/**
	 * Executes a compare operation, given the reference location for the source
	 * HTML document and destination HTML document, a writer with which to write
	 * the result to the client, and a mapping of optional arguments that may be
	 * used by the operation to configure the comparison.
	 * 
	 * @param srcDocument
	 *            the reference location, for example a URI or file path, of the
	 *            source (old) document
	 * @param destDocument
	 *            the reference location, for example a URI or file path, of the
	 *            destination (new) document
	 * @param writer
	 *            the writer with which to write the result to the client
	 * @param options
	 *            a mapping of optional arguments to supply to the operation
	 */
	protected abstract void doCompare(String srcDocument, String destDocument,
			Writer writer, Map<String, Object> options) throws Exception;

	protected abstract void doCompare(InputStream srcDocument,
			InputStream destDocument, Writer writer, Map<String, Object> options)
			throws Exception;
}
